﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Common.TextMatch
{
    /// <summary>
    /// 
    /// </summary>
    public struct TermFreq:IComparable
    {
        public string Term;
        public int Freq;

        #region Implementation of IComparable
        public int CompareTo(object obj)
        {
            if (obj.GetType() == typeof(TermFreq))
            {
                TermFreq otherTermFreq = (TermFreq)obj;
                return otherTermFreq.Freq.CompareTo(this.Freq);
            }
            else
                return 0;
        }

        //public override int GetHashCode()
        //{
        //    return this.Term.GetHashCode();
        //}

        //public override bool Equals(object obj)
        //{
        //    if(obj.GetType()==typeof(TermFreq))
        //    {
        //        TermFreq tf = (TermFreq) obj;
        //        return this.Term == tf.Term;
        //    }
        //    else
        //        return false;
        //}
        #endregion
    }
    /// <summary>
    /// 
    /// </summary>
    public class DocSimiliarityUtil
    {
        private string _Val1;
        private string _Val2;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="docContent1"></param>
        /// <param name="docContent2"></param>
        public DocSimiliarityUtil(string docContent1, string docContent2)
        {
            _Val1 = docContent1;
            _Val2 = docContent2;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public double? GetDocDistance()
        {
            Tokeniser tokeniser=new Tokeniser();
            string[] words1 = tokeniser.Partition(_Val1);
            string[] words2 = tokeniser.Partition(_Val2);
            Dictionary<string, TermFreq> termFreqs1=new Dictionary<string, TermFreq>();
            foreach(string word in words1)
            {
                string term = word.ToLower().Trim();
                if (!termFreqs1.ContainsKey(term))
                {
                    TermFreq termFreq=new TermFreq();
                    termFreq.Term = term;
                    termFreq.Freq = 1;
                    termFreqs1.Add(term, termFreq);
                }
                else
                {
                    TermFreq termFreq = termFreqs1[term];
                    termFreq.Freq += 1;
                    termFreqs1[term] = termFreq;
                }
            }
            Dictionary<string, TermFreq> termFreqs2=new Dictionary<string, TermFreq>();
            foreach (string word in words2)
            {
                string term = word.ToLower().Trim();
                if (!termFreqs2.ContainsKey(term))
                {
                    TermFreq termFreq = new TermFreq();
                    termFreq.Term = term;
                    termFreq.Freq = 1;
                    termFreqs2.Add(term, termFreq);
                }
                else
                {
                    TermFreq termFreq = termFreqs2[term];
                    termFreq.Freq += 1;
                    termFreqs2[term] = termFreq;
                }
            }
            return this.CalculateDistance(termFreqs1, termFreqs2);
        }

        private double? CalculateDistance(
            Dictionary<string, TermFreq> termFreqs1, 
            Dictionary<string, TermFreq> termFreqs2)
        {
            if (termFreqs1 == null || termFreqs2 == null ||
                termFreqs1.Count == 0 || termFreqs2.Count == 0)
                return null;

            Dictionary<string, int> unionTerms1 = new Dictionary<string, int>();
            Dictionary<string, int> unionTerms2 = new Dictionary<string, int>();
            foreach (TermFreq termFreq in termFreqs1.Values)
            {
                unionTerms1.Add(termFreq.Term, termFreq.Freq);
            }
            foreach (TermFreq termFreq in termFreqs2.Values)
            {
                unionTerms2.Add(termFreq.Term, termFreq.Freq);
                if(!unionTerms1.ContainsKey(termFreq.Term))
                    unionTerms1.Add(termFreq.Term, 0);
            }
            foreach (TermFreq termFreq in termFreqs1.Values)
            {
                if(!unionTerms2.ContainsKey(termFreq.Term))
                    unionTerms2.Add(termFreq.Term,0);
            }

            double length1 = 0;
            double length2 = 0;
            double svMul = 0;
            Dictionary<string, double> commonTerms = new Dictionary<string, double>();
            foreach (string term in unionTerms1.Keys)
            {
                double freq1 = unionTerms1[term];
                double freq2 = unionTerms2[term];
                length1 += Math.Pow(freq1, 2);
                length2 += Math.Pow(freq2, 2);
                svMul += freq2 * freq1;

                if (freq1 > 0 && freq2 > 0)
                {
                    int count = 0;
                    if (termFreqs1.ContainsKey(term) && termFreqs2.ContainsKey(term))
                        count = Math.Min(termFreqs1[term].Freq, termFreqs2[term].Freq);
                    else if (termFreqs1.ContainsKey(term))
                        count = termFreqs1[term].Freq;
                    else if (termFreqs2.ContainsKey(term))
                        count = termFreqs2[term].Freq;
                    commonTerms.Add(term, count);
                }
            }

            length1 = Math.Sqrt(length1);
            length2 = Math.Sqrt(length2);

            double cosineDistance = svMul / (length1 * length2);
            double docLenScale = (double)commonTerms.Count * 2 /
                                 (termFreqs1.Count + termFreqs2.Count - commonTerms.Count);
            cosineDistance = cosineDistance * docLenScale;

            return cosineDistance;
        }
    }
}
